package com.ruoyi.generator.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.persistence.criteria.Predicate;

import com.ruoyi.common.dao.service.UidGenService;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.core.text.CharsetKit;
import com.ruoyi.common.dao.service.BaseAppDAO;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.dao.GenTableColumnDAO;
import com.ruoyi.generator.dao.GenTableDAO;
import com.ruoyi.generator.domain.GenTable;
import com.ruoyi.generator.domain.GenTableColumn;
import com.ruoyi.generator.util.GenUtils;
import com.ruoyi.generator.util.VelocityInitializer;
import com.ruoyi.generator.util.VelocityUtils;

/**
 * 业务 服务层实现
 *
 * @author ruoyi
 */
@Service
public class GenTableServiceImpl implements IGenTableService {
	private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);

	@Autowired
	private GenTableDAO genTableDAO;

	@Autowired
	private GenTableColumnDAO genTableColumnDAO;

	@Autowired
	private BaseAppDAO baseAppDAO;

	@Autowired
	private UidGenService uidGenService;

	/**
	 * 查询业务信息
	 *
	 * @param id
	 *            业务ID
	 * @return 业务信息
	 */
	@Override
	public GenTable selectGenTableById(Long id) {
		GenTable genTable = genTableDAO.findById(id).orElse(null);
		setTableFromOptions(genTable);
		return genTable;
	}

	/**
	 * 查询业务列表
	 *
	 * @param genTable
	 *            业务信息
	 * @return 业务集合
	 */
	@Override
	public List<GenTable> selectGenTableList(GenTable genTable) {
		return genTableDAO.findAll(getSpecification(genTable));
	}


	@Override
	public Page<GenTable> selectGenTableList(GenTable genTable, Pageable pageinfo) {
		return genTableDAO.findAll(getSpecification(genTable),((PageRequest)pageinfo).withSort(Sort.Direction.DESC,"createTime"));
	}

	private Specification<GenTable> getSpecification(GenTable genTable){
		return (root, criteriaQuery, criteriaBuilder) -> {
			List<Predicate> predicateList = new ArrayList<>();
			if (!StringUtils.isEmpty(genTable.getTableName())) {
				predicateList.add(criteriaBuilder.like(criteriaBuilder.upper(root.get("tableName")), "%"+StringUtils.upperCase(genTable.getTableName())+"%"));
			}
			if (!StringUtils.isEmpty(genTable.getTableComment())) {
				predicateList.add(criteriaBuilder.like(criteriaBuilder.upper(root.get("tableComment")), "%"+StringUtils.upperCase(genTable.getTableComment())+"%"));
			}

			if (!StringUtils.isNull(genTable.getCreateTime())) {
				predicateList.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), genTable.getCreateTime()));
			}

			Predicate[] p = new Predicate[predicateList.size()];
			return criteriaBuilder.and(predicateList.toArray(p));
		};
	}

	/**
	 * 查询据库列表
	 *
	 * @param genTable
	 *            业务信息
	 * @return 数据库表集合
	 */
	@Override
	public List<HashMap> selectDbTableList(GenTable genTable) {
		String sqlquery = "select table_name as tableId, table_name as tableName, table_comment as tableComment, "
				+ " create_time as createTime, update_time as updateTime"
				+ " from information_schema.tables where table_schema = (select database()) "
				+ " AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%' "
				+ " AND table_name NOT IN (select table_name from gen_table)";
		if (StringUtils.isNotEmpty(genTable.getTableName())) {
			sqlquery += " AND lower(table_name) like lower(concat('%', '" + genTable.getTableName() + "', '%')) ";
		}
		if (StringUtils.isNotEmpty(genTable.getTableComment())) {
			sqlquery += " AND lower(table_comment) like lower(concat('%', '" + genTable.getTableComment() + "', '%')) ";
		}
		if (StringUtils.isNotNull(genTable.getParams().get("beginTime"))) {
			sqlquery += " AND date_format(create_time,'%y%m%d') >= date_format('"
					+ genTable.getParams().get("beginTime") + ")','%y%m%d') ";
		}
		if (StringUtils.isNotNull(genTable.getParams().get("endTime"))) {
			sqlquery += " AND date_format(create_time,'%y%m%d') <= date_format('" + genTable.getParams().get("endTime")
					+ "'},'%y%m%d') ";
		}
		sqlquery += " order by create_time desc ";
		List result = baseAppDAO.createNativeQuery(sqlquery);
		return result;
	}

	/**
	 * 查询据库列表
	 *
	 * @param tableNames
	 *            表名称组
	 * @return 数据库表集合
	 */
	@Override
	public List<HashMap> selectDbTableListByNames(String[] tableNames) {
		String sqlquery = "select table_name as tableName, table_comment as tableComment, "
				+ " date_format(create_time, '%Y-%m-%d %H:%I:%S') as createTime, date_format(update_time, '%Y-%m-%d %H:%I:%S') as updateTime"
				+ " from information_schema.tables where table_name NOT LIKE 'qrtz_%' "
				+ " and table_name NOT LIKE 'gen_%' and table_schema = (select database()) " + " and table_name in (";
		for (int i = 0; i < tableNames.length; i++) {
			sqlquery += "'" + tableNames[i] + "',";
		}
		sqlquery = sqlquery.substring(0,sqlquery.length() - 1);
		sqlquery += ")";
		return baseAppDAO.createNativeQuery(sqlquery);
	}

	/**
	 * 查询所有表信息
	 *
	 * @return 表信息集合
	 */
	@Override
	public List<GenTable> selectGenTableAll() {
		return genTableDAO.findAll();
	}

	/**
	 * 修改业务
	 *
	 * @param genTable
	 *            业务信息
	 * @return 结果
	 */
	@Override
	@Transactional
	public void updateGenTable(GenTable genTable) {
		String options = JSON.toJSONString(genTable.getParams());
		genTable.setOptions(options);
		genTableDAO.save(genTable);
		if (StringUtils.isNotEmpty(genTable.getColumns())){
			for (int i = 0; i < genTable.getColumns().size(); i++) {
				genTable.getColumns().get(i).setSort(i);
			}
			genTableColumnDAO.saveAll(genTable.getColumns());
		}

	}

	/**
	 * 删除业务对象
	 *
	 * @param tableIds
	 *            需要删除的数据ID
	 * @return 结果
	 */
	@Override
	@Transactional
	public void deleteGenTableByIds(Long[] tableIds) {
		genTableDAO.deleteAllByIdInBatch(Arrays.asList(tableIds));
		genTableColumnDAO.deleteByTableIdIn(tableIds);
	}

	/**
	 * 导入表结构
	 *
	 * @param tableList
	 *            导入表列表
	 */
	@Override
	@Transactional
	public void importGenTable(List<GenTable> tableList) {
		String operName = SecurityUtils.getUsername();
		try {
			for (GenTable table : tableList) {
				String tableName = table.getTableName();
				GenUtils.initTable(table, operName);
//				table.setMenuId(uidGenService.getUid());
				genTableDAO.save(table);
				// 保存列信息
				List<GenTableColumn> genTableColumns = selectDbTableColumnsByName(tableName);
				for (GenTableColumn column : genTableColumns) {
					GenUtils.initColumnField(column, table);
					genTableColumnDAO.save(column);
				}
			}
		} catch (Exception e) {
			throw new ServiceException("导入失败：" + e.getMessage());
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private List<GenTableColumn> selectDbTableColumnsByName(String tableName){
		String querysql = "select column_name, (case when (is_nullable = 'no' && column_key != 'PRI') then '1' else null end) as is_required, "
				+ " (case when column_key = 'PRI' then '1' else '0' end) as is_pk, ordinal_position as sort, column_comment, "
				+ " (case when extra = 'auto_increment' then '1' else '0' end) as is_increment, column_type "
				+ "	from information_schema.columns where table_schema = (select database()) and table_name = ('"+tableName+"') "
				+ " order by ordinal_position";
		List<HashMap> result = baseAppDAO.createNativeQuery(querysql);
		List<GenTableColumn> gentables = new ArrayList<GenTableColumn>();
		result.forEach(new Consumer<HashMap>() {
			@Override
			public void accept(HashMap arg0) {
				JSONObject json = new JSONObject(arg0);
				gentables.add(json.toJavaObject(GenTableColumn.class));
			}
		});
		return gentables;
	}
	/**
	 * 预览代码
	 *
	 * @param tableId
	 *            表编号
	 * @return 预览数据列表
	 */
	@Override
	public Map<String, String> previewCode(Long tableId) {
		Map<String, String> dataMap = new LinkedHashMap<>();
		// 查询表信息
		GenTable table = genTableDAO.findById(tableId).orElse(null);
		// 补充一下菜单id
		if(StringUtils.isEmpty(table.getMenuId())){
			table.setMenuId(uidGenService.getUid());
			table.setParams(new HashMap<String,Object>());
			table.getParams().put("querymenuId",uidGenService.getUid());
			table.getParams().put("addmenuId",uidGenService.getUid());
			table.getParams().put("editmenuId",uidGenService.getUid());
			table.getParams().put("removemenuId",uidGenService.getUid());
			table.getParams().put("exportmenuId",uidGenService.getUid());
		}
		// 设置主子表信息
		setSubTable(table);
		// 设置主键列信息
		setPkColumn(table);
		VelocityInitializer.initVelocity();

		VelocityContext context = VelocityUtils.prepareContext(table);

		// 获取模板列表
		List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
		for (String template : templates) {
			// 渲染模板
			StringWriter sw = new StringWriter();
			Template tpl = Velocity.getTemplate(template, Constants.UTF8);
			tpl.merge(context, sw);
			dataMap.put(template, sw.toString());
		}
		return dataMap;
	}

	/**
	 * 生成代码（下载方式）
	 *
	 * @param tableName
	 *            表名称
	 * @return 数据
	 */
	@Override
	public byte[] downloadCode(String tableName) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		generatorCode(tableName, zip);
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	/**
	 * 生成代码（自定义路径）
	 *
	 * @param tableName
	 *            表名称
	 */
	@Override
	public void generatorCode(String tableName) {
		// 查询表信息
		GenTable table = genTableDAO.findTop1ByTableName(tableName);
		// 设置主子表信息
		setSubTable(table);
		// 设置主键列信息
		setPkColumn(table);

		// 补充一下菜单id
		if(StringUtils.isEmpty(table.getMenuId())){
			table.setMenuId(uidGenService.getUid());
			table.setParams(new HashMap<String,Object>());
			table.getParams().put("querymenuId",uidGenService.getUid());
			table.getParams().put("addmenuId",uidGenService.getUid());
			table.getParams().put("editmenuId",uidGenService.getUid());
			table.getParams().put("removemenuId",uidGenService.getUid());
			table.getParams().put("exportmenuId",uidGenService.getUid());
		}

		VelocityInitializer.initVelocity();

		VelocityContext context = VelocityUtils.prepareContext(table);

		// 获取模板列表
		List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
		for (String template : templates) {
			if (!StringUtils.containsAny(template, "sql.vm", "api.ts.vm", "domain.d.ts.vm", "index.less.vm", "index.tsx.vm", "index-tree.tsx.vm")) {
				// 渲染模板
				StringWriter sw = new StringWriter();
				Template tpl = Velocity.getTemplate(template, Constants.UTF8);
				tpl.merge(context, sw);
				try {
					String path = getGenPath(table, template);
					FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);
				} catch (IOException e) {
					throw new ServiceException("渲染模板失败，表名：" + table.getTableName());
				}
			}
		}
	}

	/**
	 * 同步数据库
	 *
	 * @param tableName
	 *            表名称
	 */
	@Override
	@Transactional
	public void synchDb(String tableName) {
		GenTable table = genTableDAO.findTop1ByTableName(tableName);
		List<GenTableColumn> tableColumns = table.getColumns();
		Map<String, GenTableColumn> tableColumnMap = tableColumns.stream()
				.collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));

		List<GenTableColumn> dbTableColumns = selectDbTableColumnsByName(tableName);
		if (StringUtils.isEmpty(dbTableColumns)) {
			throw new ServiceException("同步数据失败，原表结构不存在");
		}
		List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName)
				.collect(Collectors.toList());

		dbTableColumns.forEach(column -> {
			GenUtils.initColumnField(column, table);
			if (tableColumnMap.containsKey(column.getColumnName())) {
				GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
				column.setColumnId(prevColumn.getColumnId());
				if (column.isList()) {
					// 如果是列表，继续保留查询方式/字典类型选项
					column.setDictType(prevColumn.getDictType());
					column.setQueryType(prevColumn.getQueryType());
				}
				if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
						&& (column.isInsert() || column.isEdit())
						&& ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
					// 如果是(新增/修改&非主键/非忽略及父属性)，继续保留必填/显示类型选项
					column.setIsRequired(prevColumn.getIsRequired());
					column.setHtmlType(prevColumn.getHtmlType());
				}
				genTableColumnDAO.save(column);
			} else {
				genTableColumnDAO.save(column);
			}
		});

		List<GenTableColumn> delColumns = tableColumns.stream()
				.filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
		if (StringUtils.isNotEmpty(delColumns)) {
			genTableColumnDAO.deleteAll(delColumns);
		}
	}

	/**
	 * 批量生成代码（下载方式）
	 *
	 * @param tableNames
	 *            表数组
	 * @return 数据
	 */
	@Override
	public byte[] downloadCode(String[] tableNames) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ZipOutputStream zip = new ZipOutputStream(outputStream);
		for (String tableName : tableNames) {
			generatorCode(tableName, zip);
		}
		IOUtils.closeQuietly(zip);
		return outputStream.toByteArray();
	}

	/**
	 * 查询表信息并生成代码
	 */
	private void generatorCode(String tableName, ZipOutputStream zip) {
		// 查询表信息
		GenTable table = genTableDAO.findTop1ByTableName(tableName);
		// 补充一下菜单id
		if(StringUtils.isEmpty(table.getMenuId())){
			table.setMenuId(uidGenService.getUid());
			table.setParams(new HashMap<String,Object>());
			table.getParams().put("querymenuId",uidGenService.getUid());
			table.getParams().put("addmenuId",uidGenService.getUid());
			table.getParams().put("editmenuId",uidGenService.getUid());
			table.getParams().put("removemenuId",uidGenService.getUid());
			table.getParams().put("exportmenuId",uidGenService.getUid());
		}
		// 设置主子表信息
		setSubTable(table);
		// 设置主键列信息
		setPkColumn(table);

		VelocityInitializer.initVelocity();

		VelocityContext context = VelocityUtils.prepareContext(table);

		// 获取模板列表
		List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
		for (String template : templates) {
			// 渲染模板
			StringWriter sw = new StringWriter();
			Template tpl = Velocity.getTemplate(template, Constants.UTF8);
			tpl.merge(context, sw);
			try {
				// 添加到zip
				zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
				IOUtils.write(sw.toString(), zip, Constants.UTF8);
				IOUtils.closeQuietly(sw);
				zip.flush();
				zip.closeEntry();
			} catch (IOException e) {
				log.error("渲染模板失败，表名：" + table.getTableName(), e);
			}
		}
	}

	/**
	 * 修改保存参数校验
	 *
	 * @param genTable
	 *            业务信息
	 */
	@Override
	public void validateEdit(GenTable genTable) {
		if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
			String options = JSON.toJSONString(genTable.getParams());
			JSONObject paramsObj = JSONObject.parseObject(options);
			if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) {
				throw new ServiceException("树编码字段不能为空");
			} else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) {
				throw new ServiceException("树父编码字段不能为空");
			} else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) {
				throw new ServiceException("树名称字段不能为空");
			} else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) {
				if (StringUtils.isEmpty(genTable.getSubTableName())) {
					throw new ServiceException("关联子表的表名不能为空");
				} else if (StringUtils.isEmpty(genTable.getSubTableFkName())) {
					throw new ServiceException("子表关联的外键名不能为空");
				}
			}
		}
	}

	/**
	 * 设置主键列信息
	 *
	 * @param table
	 *            业务表信息
	 */
	public void setPkColumn(GenTable table) {
		for (GenTableColumn column : table.getColumns()) {
			if (column.isPk()) {
				table.setPkColumn(column);
				break;
			}
		}
		if (StringUtils.isNull(table.getPkColumn())) {
			table.setPkColumn(table.getColumns().get(0));
		}
		if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
			for (GenTableColumn column : table.getSubTable().getColumns()) {
				if (column.isPk()) {
					table.getSubTable().setPkColumn(column);
					break;
				}
			}
			if (StringUtils.isNull(table.getSubTable().getPkColumn())) {
				table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
			}
		}
	}

	/**
	 * 设置主子表信息
	 *
	 * @param table
	 *            业务表信息
	 */
	public void setSubTable(GenTable table) {
		String subTableName = table.getSubTableName();
		if (StringUtils.isNotEmpty(subTableName)) {
			table.setSubTable(genTableDAO.findTop1ByTableName(subTableName));
		}
	}

	/**
	 * 设置代码生成其他选项值
	 *
	 * @param genTable
	 *            设置后的生成对象
	 */
	public void setTableFromOptions(GenTable genTable) {
		JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions());
		if (StringUtils.isNotNull(paramsObj)) {
			String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
			String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
			String treeName = paramsObj.getString(GenConstants.TREE_NAME);
			String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
			String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);

			genTable.setTreeCode(treeCode);
			genTable.setTreeParentCode(treeParentCode);
			genTable.setTreeName(treeName);
			genTable.setParentMenuId(parentMenuId);
			genTable.setParentMenuName(parentMenuName);
		}
	}

	/**
	 * 获取代码生成地址
	 *
	 * @param table
	 *            业务表信息
	 * @param template
	 *            模板文件路径
	 * @return 生成地址
	 */
	public static String getGenPath(GenTable table, String template) {
		String genPath = table.getGenPath();
		if (StringUtils.equals(genPath, "/")) {
			return System.getProperty("user.dir") + File.separator + "src" + File.separator
					+ VelocityUtils.getFileName(template, table);
		}
		return genPath + File.separator + VelocityUtils.getFileName(template, table);
	}

}